/*
* M.java
* Copyright (C) 2003
* 
* $Id: M.java,v 1.9 2006/01/21 21:53:32 salomo Exp $
*/
/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
using System;
using Defines = Suake2.UI.Defines;
using Globals = Suake2.UI.Globals;
using Suake2.UI.game;
using SV = Suake2.UI.server.SV;
using Lib = Suake2.UI.util.Lib;
using Math3D = Suake2.UI.util.Math3D;
namespace Suake2.UI.client
{
	
	/// <summary> M</summary>
	public sealed class M
	{
		public class AnonymousClassEntThinkAdapter:EntThinkAdapter
		{
			override public System.String ID
			{
				get
				{
					return "m_drop_to_floor";
				}
				
			}
			public override bool think(edict_t ent)
			{
				float[] end = new float[]{0, 0, 0};
				trace_t trace;
				
				ent.s.origin[2] += 1;
				Math3D.VectorCopy(ent.s.origin, end);
				end[2] -= 256;
				
				trace = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, end, ent, Defines.MASK_MONSTERSOLID);
				
				if (trace.fraction == 1 || trace.allsolid)
					return true;
				
				Math3D.VectorCopy(trace.endpos, ent.s.origin);
				
				GameBase.gi.linkentity(ent);
				M.M_CheckGround(ent);
				jake2.client.M.M_CatagorizePosition(ent);
				return true;
			}
		}
		public class AnonymousClassEntThinkAdapter1:EntThinkAdapter
		{
			override public System.String ID
			{
				get
				{
					return "m_fliesoff";
				}
				
			}
			public override bool think(edict_t self)
			{
				self.s.effects &= ~ Defines.EF_FLIES;
				self.s.sound = 0;
				return true;
			}
		}
		public class AnonymousClassEntThinkAdapter2:EntThinkAdapter
		{
			override public System.String ID
			{
				get
				{
					return "m_flies_on";
				}
				
			}
			public override bool think(edict_t self)
			{
				if (self.waterlevel != 0)
					return true;
				
				self.s.effects |= Defines.EF_FLIES;
				self.s.sound = GameBase.gi.soundindex("infantry/inflies1.wav");
				self.think = Suake2.UI.client.M.M_FliesOff;
				self.nextthink = GameBase.level.time + 60;
				return true;
			}
		}
		public class AnonymousClassEntThinkAdapter3:EntThinkAdapter
		{
			override public System.String ID
			{
				get
				{
					return "m_fly_check";
				}
				
			}
			public override bool think(edict_t self)
			{
				
				if (self.waterlevel != 0)
					return true;
				
				if ((float) Globals.rnd.NextDouble() > 0.5)
					return true;
				
				self.think = Suake2.UI.client.M.M_FliesOn;
				self.nextthink = GameBase.level.time + 5 + 10 * (float) Globals.rnd.NextDouble();
				return true;
			}
		}
		
		public static void  M_CheckGround(edict_t ent)
		{
			float[] point = new float[]{0, 0, 0};
			trace_t trace;
			
			if ((ent.flags & (Defines.FL_SWIM | Defines.FL_FLY)) != 0)
				return ;
			
			if (ent.velocity[2] > 100)
			{
				ent.groundentity = null;
				return ;
			}
			
			// if the hull point one-quarter unit down is solid the entity is on
			// ground
			point[0] = ent.s.origin[0];
			point[1] = ent.s.origin[1];
			point[2] = ent.s.origin[2] - 0.25f;
			
			trace = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, point, ent, Defines.MASK_MONSTERSOLID);
			
			// check steepness
			if (trace.plane.normal[2] < 0.7 && !trace.startsolid)
			{
				ent.groundentity = null;
				return ;
			}
			
			// ent.groundentity = trace.ent;
			// ent.groundentity_linkcount = trace.ent.linkcount;
			// if (!trace.startsolid && !trace.allsolid)
			//   VectorCopy (trace.endpos, ent.s.origin);
			if (!trace.startsolid && !trace.allsolid)
			{
				Math3D.VectorCopy(trace.endpos, ent.s.origin);
				ent.groundentity = trace.ent;
				ent.groundentity_linkcount = trace.ent.linkcount;
				ent.velocity[2] = 0;
			}
		}
		
		/// <summary> Returns false if any part of the bottom of the entity is off an edge that
		/// is not a staircase.
		/// </summary>
		
		public static bool M_CheckBottom(edict_t ent)
		{
			float[] mins = new float[]{0, 0, 0};
			float[] maxs = new float[]{0, 0, 0};
			float[] start = new float[]{0, 0, 0};
			float[] stop = new float[]{0, 0, 0};
			
			trace_t trace;
			int x, y;
			float mid, bottom;
			
			Math3D.VectorAdd(ent.s.origin, ent.mins, mins);
			Math3D.VectorAdd(ent.s.origin, ent.maxs, maxs);
			
			//	   if all of the points under the corners are solid world, don't bother
			//	   with the tougher checks
			//	   the corners must be within 16 of the midpoint
			start[2] = mins[2] - 1;
			for (x = 0; x <= 1; x++)
				for (y = 0; y <= 1; y++)
				{
					start[0] = x != 0?maxs[0]:mins[0];
					start[1] = y != 0?maxs[1]:mins[1];
					if (GameBase.gi.pointcontents.pointcontents(start) != Defines.CONTENTS_SOLID)
					{
						GameBase.c_no++;
						//
						//	   check it for real...
						//
						start[2] = mins[2];
						
						//	   the midpoint must be within 16 of the bottom
						start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5f;
						start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5f;
						stop[2] = start[2] - 2 * GameBase.STEPSIZE;
						trace = GameBase.gi.trace(start, Globals.vec3_origin, Globals.vec3_origin, stop, ent, Defines.MASK_MONSTERSOLID);
						
						if (trace.fraction == 1.0)
							return false;
						mid = bottom = trace.endpos[2];
						
						//	   the corners must be within 16 of the midpoint
						for (x = 0; x <= 1; x++)
							for (y = 0; y <= 1; y++)
							{
								start[0] = stop[0] = x != 0?maxs[0]:mins[0];
								start[1] = stop[1] = y != 0?maxs[1]:mins[1];
								
								trace = GameBase.gi.trace(start, Globals.vec3_origin, Globals.vec3_origin, stop, ent, Defines.MASK_MONSTERSOLID);
								
								if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
									bottom = trace.endpos[2];
								if (trace.fraction == 1.0 || mid - trace.endpos[2] > GameBase.STEPSIZE)
									return false;
							}
						
						GameBase.c_yes++;
						return true;
					}
				}
			
			GameBase.c_yes++;
			return true; // we got out easy
		}
		
		/// <summary> M_ChangeYaw.</summary>
		public static void  M_ChangeYaw(edict_t ent)
		{
			float ideal;
			float current;
			float move;
			float speed;
			
			current = Math3D.anglemod(ent.s.angles[Defines.YAW]);
			ideal = ent.ideal_yaw;
			
			if (current == ideal)
				return ;
			
			move = ideal - current;
			speed = ent.yaw_speed;
			if (ideal > current)
			{
				if (move >= 180)
					move = move - 360;
			}
			else
			{
				if (move <= - 180)
					move = move + 360;
			}
			if (move > 0)
			{
				if (move > speed)
					move = speed;
			}
			else
			{
				if (move < - speed)
					move = - speed;
			}
			
			ent.s.angles[Defines.YAW] = Math3D.anglemod(current + move);
		}
		
		/// <summary> M_MoveToGoal.</summary>
		public static void  M_MoveToGoal(edict_t ent, float dist)
		{
			edict_t goal = ent.goalentity;
			
			if (ent.groundentity == null && (ent.flags & (Defines.FL_FLY | Defines.FL_SWIM)) == 0)
				return ;
			
			//	   if the next step hits the enemy, return immediately
			if (ent.enemy != null && SV.SV_CloseEnough(ent, ent.enemy, dist))
				return ;
			
			//	   bump around...
			if ((Lib.rand() & 3) == 1 || !SV.SV_StepDirection(ent, ent.ideal_yaw, dist))
			{
				if (ent.inuse)
					SV.SV_NewChaseDir(ent, goal, dist);
			}
		}
		
		/// <summary> M_walkmove.</summary>
		public static bool M_walkmove(edict_t ent, float yaw, float dist)
		{
			float[] move = new float[]{0, 0, 0};
			
			if ((ent.groundentity == null) && (ent.flags & (Defines.FL_FLY | Defines.FL_SWIM)) == 0)
				return false;
			
			//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
			yaw = (float) (yaw * System.Math.PI * 2 / 360);
			
			//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
			move[0] = (float) System.Math.Cos(yaw) * dist;
			//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
			move[1] = (float) System.Math.Sin(yaw) * dist;
			move[2] = 0;
			
			return SV.SV_movestep(ent, move, true);
		}
		
		public static void  M_CatagorizePosition(edict_t ent)
		{
			float[] point = new float[]{0, 0, 0};
			int cont;
			
			//
			//	get waterlevel
			//
			point[0] = ent.s.origin[0];
			point[1] = ent.s.origin[1];
			point[2] = ent.s.origin[2] + ent.mins[2] + 1;
			cont = GameBase.gi.pointcontents.pointcontents(point);
			
			if (0 == (cont & Defines.MASK_WATER))
			{
				ent.waterlevel = 0;
				ent.watertype = 0;
				return ;
			}
			
			ent.watertype = cont;
			ent.waterlevel = 1;
			point[2] += 26;
			cont = GameBase.gi.pointcontents.pointcontents(point);
			if (0 == (cont & Defines.MASK_WATER))
				return ;
			
			ent.waterlevel = 2;
			point[2] += 22;
			cont = GameBase.gi.pointcontents.pointcontents(point);
			if (0 != (cont & Defines.MASK_WATER))
				ent.waterlevel = 3;
		}
		
		public static void  M_WorldEffects(edict_t ent)
		{
			int dmg;
			
			if (ent.health > 0)
			{
				if (0 == (ent.flags & Defines.FL_SWIM))
				{
					if (ent.waterlevel < 3)
					{
						ent.air_finished = GameBase.level.time + 12;
					}
					else if (ent.air_finished < GameBase.level.time)
					{
						// drown!
						if (ent.pain_debounce_time < GameBase.level.time)
						{
							//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
							dmg = (int) (2f + 2f * System.Math.Floor(GameBase.level.time - ent.air_finished));
							if (dmg > 15)
								dmg = 15;
							GameCombat.T_Damage(ent, GameBase.g_edicts[0], GameBase.g_edicts[0], Globals.vec3_origin, ent.s.origin, Globals.vec3_origin, dmg, 0, Defines.DAMAGE_NO_ARMOR, Defines.MOD_WATER);
							ent.pain_debounce_time = GameBase.level.time + 1;
						}
					}
				}
				else
				{
					if (ent.waterlevel > 0)
					{
						ent.air_finished = GameBase.level.time + 9;
					}
					else if (ent.air_finished < GameBase.level.time)
					{
						// suffocate!
						if (ent.pain_debounce_time < GameBase.level.time)
						{
							//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
							dmg = (int) (2 + 2 * System.Math.Floor(GameBase.level.time - ent.air_finished));
							if (dmg > 15)
								dmg = 15;
							GameCombat.T_Damage(ent, GameBase.g_edicts[0], GameBase.g_edicts[0], Globals.vec3_origin, ent.s.origin, Globals.vec3_origin, dmg, 0, Defines.DAMAGE_NO_ARMOR, Defines.MOD_WATER);
							ent.pain_debounce_time = GameBase.level.time + 1;
						}
					}
				}
			}
			
			if (ent.waterlevel == 0)
			{
				if ((ent.flags & Defines.FL_INWATER) != 0)
				{
					GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/watr_out.wav"), 1, Defines.ATTN_NORM, 0);
					ent.flags &= ~ Defines.FL_INWATER;
				}
				return ;
			}
			
			if ((ent.watertype & Defines.CONTENTS_LAVA) != 0 && 0 == (ent.flags & Defines.FL_IMMUNE_LAVA))
			{
				if (ent.damage_debounce_time < GameBase.level.time)
				{
					ent.damage_debounce_time = GameBase.level.time + 0.2f;
					GameCombat.T_Damage(ent, GameBase.g_edicts[0], GameBase.g_edicts[0], Globals.vec3_origin, ent.s.origin, Globals.vec3_origin, 10 * ent.waterlevel, 0, 0, Defines.MOD_LAVA);
				}
			}
			if ((ent.watertype & Defines.CONTENTS_SLIME) != 0 && 0 == (ent.flags & Defines.FL_IMMUNE_SLIME))
			{
				if (ent.damage_debounce_time < GameBase.level.time)
				{
					ent.damage_debounce_time = GameBase.level.time + 1;
					GameCombat.T_Damage(ent, GameBase.g_edicts[0], GameBase.g_edicts[0], Globals.vec3_origin, ent.s.origin, Globals.vec3_origin, 4 * ent.waterlevel, 0, 0, Defines.MOD_SLIME);
				}
			}
			
			if (0 == (ent.flags & Defines.FL_INWATER))
			{
				if (0 == (ent.svflags & Defines.SVF_DEADMONSTER))
				{
					if ((ent.watertype & Defines.CONTENTS_LAVA) != 0)
						if ((float) Globals.rnd.NextDouble() <= 0.5)
							GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/lava1.wav"), 1, Defines.ATTN_NORM, 0);
						else
							GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/lava2.wav"), 1, Defines.ATTN_NORM, 0);
					else if ((ent.watertype & Defines.CONTENTS_SLIME) != 0)
						GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/watr_in.wav"), 1, Defines.ATTN_NORM, 0);
					else if ((ent.watertype & Defines.CONTENTS_WATER) != 0)
						GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/watr_in.wav"), 1, Defines.ATTN_NORM, 0);
				}
				
				ent.flags |= Defines.FL_INWATER;
				ent.damage_debounce_time = 0;
			}
		}
		
		//UPGRADE_NOTE: The initialization of  'M_droptofloor' was moved to static method 'jake2.client.M'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		public static EntThinkAdapter M_droptofloor;
		
		public static void  M_SetEffects(edict_t ent)
		{
			ent.s.effects &= ~ (Defines.EF_COLOR_SHELL | Defines.EF_POWERSCREEN);
			ent.s.renderfx &= ~ (Defines.RF_SHELL_RED | Defines.RF_SHELL_GREEN | Defines.RF_SHELL_BLUE);
			
			if ((ent.monsterinfo.aiflags & Defines.AI_RESURRECTING) != 0)
			{
				ent.s.effects |= Defines.EF_COLOR_SHELL;
				ent.s.renderfx |= Defines.RF_SHELL_RED;
			}
			
			if (ent.health <= 0)
				return ;
			
			if (ent.powerarmor_time > GameBase.level.time)
			{
				if (ent.monsterinfo.power_armor_type == Defines.POWER_ARMOR_SCREEN)
				{
					ent.s.effects |= Defines.EF_POWERSCREEN;
				}
				else if (ent.monsterinfo.power_armor_type == Defines.POWER_ARMOR_SHIELD)
				{
					ent.s.effects |= Defines.EF_COLOR_SHELL;
					ent.s.renderfx |= Defines.RF_SHELL_GREEN;
				}
			}
		}
		
		
		//ok
		public static void  M_MoveFrame(edict_t self)
		{
			mmove_t move; //ptr
			int index;
			
			move = self.monsterinfo.currentmove;
			self.nextthink = GameBase.level.time + Defines.FRAMETIME;
			
			if ((self.monsterinfo.nextframe != 0) && (self.monsterinfo.nextframe >= move.firstframe) && (self.monsterinfo.nextframe <= move.lastframe))
			{
				self.s.frame = self.monsterinfo.nextframe;
				self.monsterinfo.nextframe = 0;
			}
			else
			{
				if (self.s.frame == move.lastframe)
				{
					if (move.endfunc != null)
					{
						move.endfunc.think(self);
						
						// regrab move, endfunc is very likely to change it
						move = self.monsterinfo.currentmove;
						
						// check for death
						if ((self.svflags & Defines.SVF_DEADMONSTER) != 0)
							return ;
					}
				}
				
				if (self.s.frame < move.firstframe || self.s.frame > move.lastframe)
				{
					self.monsterinfo.aiflags &= ~ Defines.AI_HOLD_FRAME;
					self.s.frame = move.firstframe;
				}
				else
				{
					if (0 == (self.monsterinfo.aiflags & Defines.AI_HOLD_FRAME))
					{
						self.s.frame++;
						if (self.s.frame > move.lastframe)
							self.s.frame = move.firstframe;
					}
				}
			}
			
			index = self.s.frame - move.firstframe;
			if (move.frame[index].ai != null)
				if (0 == (self.monsterinfo.aiflags & Defines.AI_HOLD_FRAME))
					move.frame[index].ai.ai(self, move.frame[index].dist * self.monsterinfo.scale);
				else
					move.frame[index].ai.ai(self, 0);
			
			if (move.frame[index].think != null)
				move.frame[index].think.think(self);
		}
		
		/// <summary>Stops the Flies. </summary>
		//UPGRADE_NOTE: The initialization of  'M_FliesOff' was moved to static method 'jake2.client.M'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		public static EntThinkAdapter M_FliesOff;
		
		/// <summary>Starts the Flies as setting the animation flag in the entity. </summary>
		//UPGRADE_NOTE: The initialization of  'M_FliesOn' was moved to static method 'jake2.client.M'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		public static EntThinkAdapter M_FliesOn;
		
		/// <summary>Adds some flies after a random time </summary>
		//UPGRADE_NOTE: The initialization of  'M_FlyCheck' was moved to static method 'jake2.client.M'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		public static EntThinkAdapter M_FlyCheck;
		static M()
		{
			M_droptofloor = new AnonymousClassEntThinkAdapter();
			M_FliesOff = new AnonymousClassEntThinkAdapter1();
			M_FliesOn = new AnonymousClassEntThinkAdapter2();
			M_FlyCheck = new AnonymousClassEntThinkAdapter3();
		}
	}
}